贝叶斯新闻分类实战项目详解
(给Python开发者加星标,提升Python技能)
来源:Be_melting
https://blog.csdn.net/lys_828/article/details/108990366
【导读】:今日头条、UC头条等,只要是打开以后,各种新闻就已经分好类了,根据自己喜好查看相应的新闻内容。社会、娱乐、电影等各种分类,数以亿计的新闻,通过机器学习完成分类。那么我们自己能不能自己做一个新闻分类的应用呢?答案是肯定的,本文将带领大家学习基于贝叶斯方法的新闻分类实战,快来学习吧。
--- 以下是正文 ---
贝叶斯方法简介
关于贝叶斯要解决的问题(贝叶斯方法源于他生前为解决“逆概”问题写的一篇文章):
正向概率:假设袋里面有N个白球,M个黑球,伸手进去摸一把,摸出黑球的概率有多
逆向概率:如果我们事先并不知道袋子里面黑白球的比例,而是闭着眼睛摸出一个(或者好几个球),观察这些取出来球的颜色后,那么我们可以就此对袋子里面的黑白球的比例作出什么样的推测,还是举个具体的例子进行理解逆向概率,如下:
假设学校男生占60%,女生占40%,男生总是喜欢穿长裤,而女生则是一半穿长裤一半穿裙子
正向概率:随机选取一个学生,其穿长裤的概率和穿裙子的概率有多大?
逆向概率:迎面走来了一个穿长裤的学生,你只看见其穿的是长裤,而无法确定其性别,那么可以推断其是女生的概率有多大?
贝叶斯方法解决的问题就是上面举例中的逆向概率的求解。新闻分类也是一样,根据一篇新闻的内容,推断其最有可能哪个类别。下面就开始新闻分类的实战之旅。
新闻分类实战
1. 文本分词
就是将获取到的新闻数据,转化成有一个个词语组成的样式。新闻的数据是由搜狗实验室提供,可以根据链接进行获取,这里使用的数据是已经经过清洗后的数据,直接进行加载(关于文章中使用的数据,包括处理后的新闻数据和停用词数据,已经上传至资源,可以自行下载)。
1.1 数据加载
#既然涉及到分词,最常用,也是最成熟的一个包就是jieba了
import pandas as pd
import jieba
import os
os.chdir(r'C:\Users\86177\Desktop\机器学习案例\新闻分类')
df_news = pd.read_table('./data/data.txt',names=['category','theme','URL','content'],encoding='utf-8')
df_news = df_news.dropna()
df_news.tail()
输出的结果为:(Category:当前新闻所属的类别,一会我们要进行分别任务,这就是标签了。Theme:新闻的主题,这个咱们先暂时不用,大家在练习的时候也可以把它当作特征。URL:爬取的界面的链接,方便检验爬取数据是不是完整的,这个咱们暂时也不需要。Content:新闻的内容,这些就是一篇文章了,里面的内容还是很丰富的。)
既然数据导入之后,就看一下数据的规模。
df_news.shape
#(5000, 4)
#数据量这里就只导入了5000条,特征的话就是4个
接下来任务已经很明确了,根据文章的内容来进行类别的划分。那如何做这个事呢?咱们之前看到的数据都是数值的,直接传入到算法中求解参数就可以了。这份数据就有些特别了,都是文本,计算机可不认识这些文字的,所以我们需要把这些文字转换成特征,比如说将一篇文章转换成一个向量,这样就可以玩起来了。对于一篇文章来说,里面的内容还是有点太多了,如果直接把它转换成向量,有一串数字来表示这篇文章,一方面来说难度有些大,另一方面这些表示的效果也不尽如人意。通常的做法都是先把文章进行分词,然后在词的层面上去做文章。先拿到一篇文章,然后我们再看看其分词的结果。
content = df_news.content.values.tolist() #将每一篇文章转换成一个list
print (content[4999]) #随便选择其中一个看看,就是最后一篇新闻
输出的结果为:(在进行分词前要将多篇新闻数据放在一个列表中,方便后序的遍历。)
1.2 jieba分词基本步骤
使用jieba分词筛选结果的模板(标准方式),共五步:
在遍历数据之前创建一个空列表,用于存放数据。
然后在依次遍历存放新闻数据的列表,获取每一篇新闻数据。
使用jieba.lcut()方法对文本进行分词。
接着就是剔除掉字符个数小于等于1个的词语,还有顺带去掉换行符(也可以按照自己的格式要求进行剔除)。
最后就是将满足的结果添加到最初创建的空列表中。
content_S = []
for page in content:
current_segment = jieba.lcut(page) #对每一篇文章进行分词
if len(current_segment) > 1 and current_segment != '\r\n': #换行符
content_S.append(current_segment) #保存分词的结果
content_S[4999]
#['全球','最美','女人','合成图',':',':', '国','整形外科','教授' '李承哲',',','在','国际', '学术','杂志','美容','整形外科',
#'学会','学报','发表','了','考虑','种族','和','民族','脸型','多样性','的','黑人','、','白人','、','中国',
#'人','、','日本','人','女性','“','最具','魅力','的','脸型','”','的','论文','。','李承哲','合成','这些','脸型','时','采用',
#'的','演艺人','脸型','有',',','黑人','1','3','名','、','白','1','6','名','、','中国','人','2','0','名','、','日本',
#'人','1','4','名','等','共','6','3','名','的','脸型','。','M','贾凶','宰笙','蛴','乙来','挝',':','黑人','美女','、',
#'白人','美女','、','中国','美女','、','日本','美女','和','韩国','美女','。']
因为分词后的内容是列表,可以直接使用字典列表的形式创建DataFrame,而字典的键就是分词的结果的列名。
df_content=pd.DataFrame({'content_S':content_S}) #专门展示分词后的结果
df_content.head()
输出结果为:
创建的DataFrame数据可以用于绘制词云图,从分词后的结果可以看出,虽然剔除了部分数据,但是还是有很多不是我们想看到的内容,比如冒号空格,句号空格等。因此绘制词云图之前需要先剔除一些常见的不是很重要的词语,前面的是初加工筛选,这里就要精加工筛选了,为了避免一个个手动剔除常见的一些不重要的词汇,所以就有了停用词字典这个概念(就是把日常的词汇收集在一起,这个字典不是指python的字典数据类型,而是指新华字典,用于存放收集的数据的地方),凡是分词结果后的词语在这个停用词范围中的,都将被舍弃。
1.3 加载停用词字典数据进行数据清洗
stopwords=pd.read_csv("stopwords.txt",index_col=False,sep="\t",quoting=3,names=['stopword'], encoding='utf-8')
stopwords.head(10)
输出结果为:(前面的一些停用词都是标点符号,后面的都是常见的汉字词语,文件已经上传到我的资源。)
为了方便日后的使用,这里直接封装函数,只需要传入相对应格式的数据即可(数据就是新闻数据的列表,和停用词的列表。)
def drop_stopwords(contents,stopwords):
contents_clean = []
all_words = []
for line in contents:
line_clean = []
for word in line:
if word in stopwords:
continue
line_clean.append(word)
all_words.append(str(word))
contents_clean.append(line_clean)
return contents_clean,all_words
contents = df_content.content_S.values.tolist()
stopwords = stopwords.stopword.values.tolist()
contents_clean,all_words = drop_stopwords(contents,stopwords)
将处理后的结果转化为DataFrame数据:
df_content=pd.DataFrame({'contents_clean':contents_clean})
df_content.tail()
输出结果为:(这里的数据是将每篇新闻分词后清洗的结果重新放置在列表中。)
df_all_words=pd.DataFrame({'all_words':all_words})
df_all_words.tail()
输出结果为:(这里的数据是将所有分词的结果以单个数据的形式都保存起来了。)
1.4 词云图绘制
那么接下来就可以使用数据绘制词云图,既可以选择按照每篇新闻分词的数据,也可以使用全部词语的数据,下面就使用所有词语的数据进行绘制,基本步骤就是四步:
首先截取要呈现的目标数据(一般20,50或者100,根据需要选定)。
词云图对象初始化(这里可以对词云图的参数进行设置)。
将选取的数据传入到fit.words()方法中。
最后就是将词云图展示出来。
from wordcloud import WordCloud
import matplotlib.pyplot as plt
%matplotlib inline
import matplotlib
matplotlib.rcParams['figure.figsize'] = (10.0, 5.0)
woordcloud_data = df_all_words.all_words.value_counts()[:100]
wordcloud=WordCloud(font_path="./data/simhei.ttf",background_color="white",max_font_size=80)
wordcloud=wordcloud.fit_words(woordcloud_data)
plt.imshow(wordcloud)
输出的结果为:(可以发现所有数据的词云中,一个个单词还是很多的。)
2. TF-IDF:提取关键词
解释一下TF-IDF是一个什么东西,还是进行举例子,假设一篇文章为《中国的蜜蜂养殖》
当我进行词频统计的时候,发现这篇文章中,‘中国’,‘蜜蜂’,‘养殖’这三个词出现的次数(TF就代表这出现的词频)是一样的,比如都是10次,那这个时候如果判断其重要性呢?这一篇文章应该讲述的是都跟蜜蜂和养殖相关的技术,所以这俩词应当是重点了。而中国这个词,我们既可以说中国的蜜蜂,还可以说中国的篮球,中国的大熊猫,能派上用场的地方简直太多了,没有那么专一,所以在这篇文章中它应当不是那么重要的。这里我们就可以给出一个合理的定义了,如果一个词在整个语料库中(可以当作是在所有文章中)出现的次数都很高(这篇也有它,另一片还有这个词),那么这个词的重要程度就不高,因为它更像一个通用词。如果另一个词在整体的语料库中的词频很低,但是在这一篇文章中的词频却很高(这个就是IDF逆文本频率),我们就有理由认为它在这篇文章中就很重要了。比如蜜蜂这个词,在篮球,大熊猫相关的文章中基本不可能出现,这里却大量出现了。因此就可以使用TF-IDF进行文本关键词的提取。
import jieba.analyse #工具包
index = 2400 #随便找一篇文章就行
content_S_str = "".join(content_S[index]) #把分词的结果组合在一起,形成一个句子
print (content_S_str) #打印这个句子
print (" ".join(jieba.analyse.extract_tags(content_S_str, topK=5, withWeight=False)))#选出来5个核心词
输出的结果为:(根据汇总出的五个关键词可以发现,基本上可以概括出这篇新闻的内容,主要围绕着赞助的问题在比赛球衣上进行宣传。)
由上面的分类看出,通过jieba可以直接提取文本的特征,但是要进行文本的分类,对于计算机来讲的话,它并不认识这些汉字,它只认识数值,有没有可能性将汉字都转化为数字,用数字进行表示呢?那么词袋模型就可以解决这个问题。
既然是进行文本分类,首先要做的就是进行标签制作,这里就按照常见的是个新闻标签进行对应新闻的匹配。
df_train=pd.DataFrame({'contents_clean':contents_clean,'label':df_news['category']})
df_train.tail()
输出结果为:(可以发现最后一列就是对应的标签,但是需要将其转化为计算机识别的数值。)
因此就需要人为手动的进行映射关系的处理。
#df_train.label.unique() 查看总共有多少个唯一的标签,然后在进行映射
label_mapping = {"汽车": 1, "财经": 2, "科技": 3, "健康": 4, "体育":5, "教育": 6,"文化": 7,"军事": 8,"娱乐": 9,"时尚": 0}
df_train['label'] = df_train['label'].map(label_mapping) #构建一个映射方法
df_train.tail()
输出结果为:(这样就实现标签的映射了。)
标签处理好了之后就是进行数据特征的处理,那么就有个问题,每篇的新闻的字数是不一样的,有的是200词,有的是300词,有的甚至更多,但是在机器学习中要求输入的数据的维度是统一的,因此就得把数据的维度化为统一,那么就需要找一个可以进行衡量的支点,而这个支点就是语料库,即使每篇文章的数据的长短不一样,但是由所有文章组成的语料库的容量是一定了,所以判断每篇文章中数据是否在语料库中既可以对应上,也就转化为同一维度了,为了方便理解还是进行举例说明。
from sklearn.feature_extraction.text import CountVectorizer
texts=["dog cat fish","dog cat cat","fish bird", 'bird'] #为了简单期间,这里4句话我们就当做4篇文章了
cv = CountVectorizer() #词频统计
cv_fit=cv.fit_transform(texts) #转换数据
print(cv.get_feature_names())
print(cv_fit.toarray())
print(cv_fit.toarray().sum(axis=0))
输出的结果为:(语料库就是有四个单词组成的,可以通过cv.get_feature_names()
获取所有文章组成的语料库,这个语料库中包含所有的词汇,cv_fit.toarray()
就可以将每篇的新闻数据转化为array数组,实现字母/汉字与数值的对应,当出现相同的内容时候,数值会加1,最后也可以进行多篇新闻数据的单词频数汇总。)
['bird', 'cat', 'dog', 'fish']
[[0 1 1 1]
[0 2 1 0]
[1 0 0 1]
[1 0 0 0]]
[2 3 2 2]
经过结果的分析,可以归纳出词袋模型的特征:说白了它就是看每一个词出现几次,来统计词频就可以了,再把所有出现的词组成特征的名字,依次统计其个数就可以得到文本特征了。这里给我们的感觉有点过于简单了,只考虑词频而不考虑词出现的位置以及先后顺序,那能不能稍微再改进一些呢?这里我们还可以通过设置ngram_range
来控制特征的复杂度,比如我们不光可以考虑单单一个词,还可以考虑两个词连在一起,甚至更多的词连在一起。
from sklearn.feature_extraction.text import CountVectorizer
texts=["dog cat fish","dog cat cat","fish bird", 'bird']
cv = CountVectorizer(ngram_range=(1,4)) #设置ngram参数,让结果不光包含一个词,还有2个,3个的组合
cv_fit=cv.fit_transform(texts)
print(cv.get_feature_names())
print(cv_fit.toarray())
输出的结果为:(可以发现,设置了参数保留1-4个词汇特征的时候,原来语料库中只有四个单词的,现在直接扩充2.5倍,这种方式是可以标记对于文本中出现的词汇顺序,但是如果一篇文章就有很多单词,统计5000篇就可能超过10w,那么再设置ngram_range参数很大的话,就可能导致语料库达到几千万的容量,因此这里可以考虑单词出现的顺序,设置为2就可以了,然后在创建语料库的时候加一些限制,过滤掉词频较小的词汇。)
['bird', 'cat', 'cat cat', 'cat fish', 'dog', 'dog cat', 'dog cat cat', 'dog cat fish', 'fish', 'fish bird']
[[0 1 0 1 1 1 0 1 1 0]
[0 2 1 0 1 1 1 0 0 0]
[1 0 0 0 0 0 0 0 1 1]
[1 0 0 0 0 0 0 0 0 0]]
那么就开始拿新闻数据进行实践,首先就是划分训练集和测试集。
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(df_train['contents_clean'].values, df_train['label'].values, random_state=1)
接着进行把新闻数据的类型由列表全部转化为指定的格式(列表中嵌套着字符串数据。)
test_words = []
for line_index in range(len(x_test)):
try:
#
test_words.append(' '.join(x_test[line_index]))
except:
print (line_index,word_index)
print(type(test_words))
test_words[0]
如果不进行词频较小的特征过滤,看一下生成的语料库的,然后再对比一下进行过滤后的。
from sklearn.feature_extraction.text import CountVectorizer
vec = CountVectorizer(analyzer='word',lowercase = False)
feature = vec.fit_transform(words)
print(feature.shape)
vec = CountVectorizer(analyzer='word', max_features=4000, lowercase = False)
feature = vec.fit_transform(words)
print(feature.shape)
输出结果为:(可以发现如果不进行限制的话,3750篇文章共创建的语料库的容量为8w多,而限制保留指定的特征后,就会选取词汇频数最大的4000个进行保留。可以想象一下加入一篇文章有一千个字,那么对应8w,生成数组的结果肯定会有很多很多0,导致特征过于稀疏,所以有必要对创建的语料库容器进行限制。)
(3750, 85093)
(3750, 4000)
最后一步就是实现词袋模型的特征建模,导入贝叶斯模型。
from sklearn.naive_bayes import MultinomialNB #贝叶斯模型
classifier = MultinomialNB()
classifier.fit(feature, y_train)
前面将训练集的新闻数据转化为了制定的数据格式,那么对于要进行检测的数据也需要转化为一致的格式,因此还要执行和上面一样的操作,但是这次导入的数据是测试数据。
test_words = []
for line_index in range(len(x_test)):
try:
#
test_words.append(' '.join(x_test[line_index]))
except:
print (line_index,word_index)
test_words[-1]
输出结果为:(至此,模型也训练好了,测试数据也化为统一格式了,那么接下来就可以进行模型得分的评测。)
classifier.score(vec.transform(test_words), y_test)
输出结果为:0.8032
那么就可以用这个模型进行预测一下,看看文本分类的结果。
classifier.predict(vec.transform(test_words)[-1])
输出结果为:
array([7], dtype=int64)
而直接查看一下对应的标签,并对应上最初的映射结果。
print(y_test[-1])
print(['{}:{}'.format(key,value) for key,value in filter(lambda x:7 == x[1], label_mapping.items())])
输出的结果为:(根据上面的新闻数据的输出可知,是进行中国京剧的介绍的,归类为文化是没有问题的。)
7
['文化:7']
4. TF-IDF制作特征
前面已经讲解到了TF-IDF,不过是直接用来提取特征,那么接下来介绍一下它怎么制作特征,还是进行举例,这次就简单的两句话。
from sklearn.feature_extraction.text import TfidfVectorizer
X_test = ['卡尔 敌法师 蓝胖子 小小','卡尔 敌法师 蓝胖子 痛苦女王']
tfidf=TfidfVectorizer()
weight=tfidf.fit_transform(X_test).toarray()
word=tfidf.get_feature_names()
print (weight)
for i in range(len(weight)):
print (u"第", i, u"篇文章的tf-idf权重特征")
for j in range(len(word)):
print (word[j], weight[i][j])
输出的结果为:(和之前的 CountVectorizer类似,之前计算的是出现的频次,这里除了考虑词频TF,还有IDF逆文本频率,最后按照权重进行赋值。)
[[0.44832087 0.63009934 0.44832087 0. 0.44832087]
[0.44832087 0. 0.44832087 0.63009934 0.44832087]]
第 0 篇文章的tf-idf权重特征
卡尔 0.44832087319911734
小小 0.6300993445179441
敌法师 0.44832087319911734
痛苦女王 0.0
蓝胖子 0.44832087319911734
第 1 篇文章的tf-idf权重特征
卡尔 0.44832087319911734
小小 0.0
敌法师 0.44832087319911734
痛苦女王 0.6300993445179441
蓝胖子 0.44832087319911734
接下来就可以使用TF-IDF特征建模来观察结果,和之前的词袋模型的代码很类似,就是换了几个单词。
vectorizer = TfidfVectorizer(analyzer='word', max_features=4000, lowercase = False)
vectorizer.fit(words)
classifier = MultinomialNB()
classifier.fit(vectorizer.transform(words), y_train)
classifier.score(vectorizer.transform(test_words), y_test)
输出结果为:0.8136 (比之前的模型得分要好一点)
至此关于贝叶斯新闻分类实战项目梳理就到此完结了,撒花撒花✿✿ヽ(°▽°)ノ✿
总结
本文是对贝叶斯新闻分类实战项目进行梳理,使用贝叶斯新闻分类深度讲解。重点在于一下的几个知识点:
jieba分词的步骤。
加载停用词进行分词后文本数据的清洗。
词袋模型的理解。
TF-IDF提取和制作特征。
贴标签和根据标签找文本数据。
- EOF -
觉得本文对你有帮助?请分享给更多人
关注「Python开发者」加星标,提升Python技能
点赞和在看就是最大的支持❤️